/*
  Copyright (C) 2001-2003 Renaud Pawlak.

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU Lesser General Public License as
  published by the Free Software Foundation; either version 2 of the
  License, or (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

package org.objectweb.jac.util;

import java.io.*;
import java.util.*;
import java.lang.reflect.*;

/**
 * This class generates the <code>jac.lib</code> delegators
 * source-files from the <code>jac.prop</code> file found in the
 * current directory (exclude all other options).
 *
 * @author Renaud Pawlak */

public class WrapLib {

   public static String listAsString(List l) {
      String s="[";
      for(int i=0;i<l.size();i++) {
         s=s+l.get(i);
         if(i+1<l.size()) s=s+",";
      }
      return s;
   }

   /**
    * The entry point of the wraplib program.
    *
    * @param args none arguments are expected (parametrization is done
    * by the <code>jac.prop</code> file in the current directory --
    * user should define the jac.toWrap property as a list of classes
    * to wrap */

   public static void main(String[] args) throws Throwable {
      String propFileName = "jac.prop";
      String toAdaptProp = "jac.toWrap";
      Properties props;
      Vector classesToAdapt = new Vector();
      /** Try to load a jac.prop file from the current directory */
      try {
	
         FileInputStream fis = new FileInputStream( propFileName );
         
         props = new Properties();
         props.load( fis );
         String prop = props.getProperty(toAdaptProp);
         
         if ( prop == null ) {
            System.out.println( "-- ERROR: no property jac.toAdapt found in property file "+propFileName );
            System.exit(-1);
            
         } else {
            
            StringTokenizer st = new StringTokenizer( prop );
            while ( st.hasMoreElements() )
               classesToAdapt.add( st.nextElement() );
         }
         
         
            for ( int i = 0; i < classesToAdapt.size(); i++ ) {
               createDelegator( Class.forName( (String) classesToAdapt.get(i) ));
            }
            
      }
      catch( FileNotFoundException e ) {
         System.out.println( "-- ERROR: property file "+propFileName+" not found" );
         System.exit(-1);
      }
      catch( Exception e ) { e.printStackTrace(); }
   }

   /**
    * Creates a class that present the same interface that the
    * original class but that delegates all the work to an instance of
    * the original class.
    * 
    * <p>This feature is implemented to be able to easily wrap
    * libraries that would have been hardly wrappable on the "as is"
    * classes, for instance the jdk classes (see the -g option of Jac).
    *
    * <p>As a result, this method creates a Java source file in the
    * src/org/objectweb/jac/lib directory of the JAC distribution. This file should
    * then be compiled as a regular file.
    *
    * @param c the original class */

   protected static void createDelegator( Class c ) {
      try {
         System.out.println("-- Generating delegator version of " + c + ".");
         File f = new File( 
            "src/org/objectweb/jac/lib/" + c.getName().replace('.','/') + ".java" );
         if ( f.exists() )
            f.delete();
         f.getParentFile().mkdirs();
         f.createNewFile();
         String shortName = c.getName().substring( c.getName().lastIndexOf('.') + 1 );
         FileWriter fw = new FileWriter( f );
         fw.write( "/**\n" +
                   " * This class delegates to " + c.getName() + "\n" +
                   " * This file was automatically generated by JAC (-g option)\n" +
                   " * DO NOT MODIFY\n" +
                   " * Author: Renaud Pawlak (pawlak@cnam.fr)\n" +
                   " */\n" );
         fw.write( "\npackage jac.lib." + c.getPackage().getName() + ";\n" );
         fw.write( "\npublic class " + shortName + ((c.getInterfaces().length == 0)? "" : " implements " +
                   arrayToString( createArray( c.getInterfaces() ) )) );
         fw.write( " {\n" );
         fw.write( "\n    private " + c.getName() + " delegate = new " + c.getName() + "();\n" );
         fw.write( "\n    public Object clone() {\n        Object result=null;\n        try { result=super.clone(); } catch(Exception e) {};\n        (("+ shortName +")result).delegate=("+c.getName()+")delegate.clone();\n        return result;\n    }\n");
	 fw.write( "\n    public boolean equals(Object o1) {\n        return (delegate==null?super.equals(o1):delegate.equals(o1));\n    }\n");
         //fw.write( "\n    public String toString() {\n        return delegate.toString();\n    }\n");
         Method[] ms = c.getMethods();
         for ( int i = 0; i < ms.length; i++ ) {
            int mod = ms[i].getModifiers();
            if( Modifier.isPrivate(mod) || Modifier.isAbstract(mod) ||
                Modifier.isInterface(mod) || Modifier.isProtected(mod) ||
                Modifier.isStatic(mod) ) continue;
            if( ms[i].getName().equals("clone") ) continue;
            //if( ms[i].getName().equals("hashCode") ) continue;
            if( ms[i].getDeclaringClass() == Object.class ||
                ms[i].getDeclaringClass().isInterface() ) continue;
            
            try {
               Object.class.getMethod( ms[i].getName(), ms[i].getParameterTypes() );
               continue;
            } catch ( Exception e ) {}

            fw.write( "\n    " + getMethodPrototype( ms[i] ) + " {\n" );
            if ( ms[i].getReturnType().getName().equals( "void" ) ) {
               //               fw.write( "        if ( delegate == null ) return;\n" );
               fw.write( "        delegate." + ms[i].getName() + "(" +
               arrayToString( createParameterNames( ms[i].getParameterTypes() ) ) + ");\n" );
            } else {
               //               fw.write( "        if ( delegate == null ) return " + 
               //                         getDefaultValueFor( ms[i].getReturnType() ) + ";\n" );
               fw.write( "        return delegate." + ms[i].getName() + "(" +
               arrayToString( createParameterNames( ms[i].getParameterTypes() ) ) + ");\n" );
            }
            fw.write( "    }\n" );
         }
         fw.write( "}\n" );
         fw.close();
      } catch ( Exception e ) {
         e.printStackTrace();
      }
   }

   /**
    * Return the default string value that should return a method with
    * a <code>c</code> return type.
    *
    * <p>By default:
    *
    * <ul><pre>
    * - c == boolean => "false"
    * - c == char => "''"
    * - c == byte => "0"
    * - c == all other primitive types => "-1"
    * - c is an object => "null"
    * </pre></ul>
    *
    * @param c the type
    * @return the default string value for this type */

   private static String getDefaultValueFor( Class c ) {
      if ( c.isPrimitive() ) {
         if (c == Boolean.TYPE )
            return "false";
         if (c == Character.TYPE )
            return "''";
         if ( c == Byte.TYPE ) 
            return "0";
         if (c == Short.TYPE || c == Integer.TYPE || 
             c == Long.TYPE || c == Float.TYPE || c == Double.TYPE )
            return "-1";
      }
      return "null";
   }

   /**
    * Return a Java-syntax-compiliant string representation of the
    * given method.
    *
    * @param m the method
    * @return the string representation
    */

   private static String getMethodPrototype( Method m ) {
      return "public " + createStringFor( m.getReturnType() ) + " " + m.getName() + 
         " ( " + arrayToString( createTypedParameterNames( m.getParameterTypes() ) ) +
         " ) " + arrayToString( createArray( m.getExceptionTypes() ) );
   }
   
   /**
    * Return a comma-separated string for a string array.
    *
    * <p>For instance:
    *
    * <ul><pre>
    * - {"a", "b", "c"} => "a, b, c"
    * - {"a"} => "a"
    * - {} => ""
    * </pre></ul>
    *
    * @param array the given array
    * @return its string representation
    */

   private static String arrayToString( String[] array ) {
      if ( array.length == 0 ) return "";
      String ls = java.util.Arrays.asList(array).toString();
      return ls.substring( 1, ls.length() - 1 );
   }

   /**
    * Create an array of Java-syntax-compiliant string from an array of types.
    *
    * <p>For instance:
    *
    * <ul><pre>
    * - {class int, class java.lang.object} => {"int", "java.lang.Object"}
    * - {class [java.lang.object} => {"java.lang.Object[]"}
    * </pre></ul>
    * 
    * @param array the types
    * @return the corresponding strings
    *
    * @see #createStringFor(Class)
    */
   
   private static String[] createArray( Class[] array ) {
      String[] res = new String[array.length];
      for ( int i = 0; i < array.length; i ++ ) {
         res[i] = createStringFor( array[i] );
      }
      return res;
   }

   /**
    * Create a Java-syntax-compiliant string from a type.
    *
    * <p>For instance:
    *
    * <ul><pre>
    * - class java.lang.object => "java.lang.Object"
    * - class [java.lang.object => "java.lang.Object[]"
    * </pre></ul>
    * 
    * @param c the type
    * @return the corresponding string
    */

   private static String createStringFor ( Class c ) {
      if ( c.isArray() ) {
         return c.getComponentType().getName() + "[]";
      } else {
         return c.getName();
      }
   }

   /**
    * Create a Java-syntax-compiliant string for a method that would
    * take a set of parameters defined in <code>array</code>.
    *
    * <p>The parameter names are generated to "p[0-array.length]". For
    * instance:
    *
    * <ul><pre>
    * - {class int, class byte} => {"int p0", "byte p1"}
    * </pre></ul>
    *
    * @param array the types
    * @param the resulting strings
    *
    * @see #createParametersName( array ) */

   private static String[] createTypedParameterNames ( Class[] array ) {
      String[] res = new String[array.length];
      for ( int i = 0; i < array.length; i ++ ) {
         res[i] = createStringFor( array[i] ) + " p" + i;
      }
      return res;
   }

   /**
    * Create untyped paramameter names that correspond to the names
    * that would have been given by the
    * <code>createTypedParameterNames</code>.
    *
    * <p>For instance, an array of 2 types always returns:
    *
    * <ul><pre>
    * {"p0, "p1"}
    * </pre></ul>
    *
    * @param array the types
    * @param the resulting strings
    *
    * @see #createTypedParametersName( array ) */

   private static String[] createParameterNames ( Class[] array ) {
      String[] res = new String[array.length];
      for ( int i = 0; i < array.length; i ++ ) {
         res[i] = "p" + i;
      }
      return res;
   }
   
}

